// 文件传输数据块
package cfiles

import (
	"bufio"
	"context"
	"fmt"
	"gomeshsync/cfile"
	"gomeshsync/config"
	"gomeshsync/consts"
	"log"
	"os"
	"strconv"
	"time"

	"gitee.com/kimsoso/go-utils/sonet"
)

type FileBlock struct {
	bchan chan CBlock
}

var (
	fileblockSingle FileBlock
)

func init() {
	fileblockSingle = FileBlock{
		bchan: make(chan CBlock, 8192),
	}
}

func NewFileBlock() FileBlock {
	return fileblockSingle
}

// 增加数据块
func (fb FileBlock) AddBlock(cb CBlock) {
	fb.bchan <- cb
}

// 下载文件数据块
func (fb FileBlock) DownloadBlock(cancelCtx context.Context) {
	for {
		select {
		case <-cancelCtx.Done():
			return
		default:
			block, ok := <-fb.bchan
			if ok {
				c := config.GetConfig()
				fb.downloadBlockFromNodes(block, c)
				c.SleepForRoutine()
			}
		}
	}
}

// 从远端下载数据块
func (fb FileBlock) downloadBlockFromNodes(block CBlock, conf *config.Config) {
	dstFile, err := os.OpenFile(toRealSvrPath(block.filePath), os.O_WRONLY, os.ModePerm)
	if err != nil {
		log.Println("open md5 file failed, err:", err)
		return
	}
	defer dstFile.Close()

	bufWriter := bufio.NewWriter(dstFile)
	for {
		node := config.GetNextNode()
		if fb.doDownloadBlock(node, block, dstFile, *bufWriter, conf) {
			SetFileMaskBit(block.filePath, block.seq, cfile.BitAdd)
			break
		}
		conf.SleepForRoutine()
	}
}

// download block from specific node
func (fb FileBlock) doDownloadBlock(node string, block CBlock, file *os.File, bufWriter bufio.Writer, conf *config.Config) bool {
	//连接node
	conn, err := timeoutConn(node, conf.DataPort)
	if err != nil {
		log.Println("connect to "+node+" error", err)
		return false
	}
	defer func() {
		defer bufWriter.Flush()
		conn.Close()
	}()

	err = sonet.Write(conn, []byte(consts.COMM_REVBLOCK+":"+block.filePath+":"+strconv.Itoa(block.seq)))
	if err != nil {
		return false
	}

	_, buf, err := sonet.Read(conn)
	if err != nil {
		return false
	}
	//下面只处理，返回拥有数据块和没有该数据块但拥有其他数据块的情况，其他情况默认发送关闭连接
	if string(buf) == consts.COMM_OK {
		//发出待接收数据详细信息, offset:blocksize
		err := sonet.Write(conn, []byte(fmt.Sprintf("%s:%s:%d:%d", consts.COMM_ReqSendBlock, block.filePath, block.offset, block.size)))
		if err != nil {
			return false
		}

		file.Seek(block.offset, 0)
		var nSizeWrote int64 = 0

		var start time.Time
		//读取网络数据并写入本地文件
		return sonet.ReceiveLongData(conn, func(buf []byte) bool {
			start = time.Now()

			nn, err := bufWriter.Write(buf)
			if err != nil {
				return false
			}
			nSizeWrote += int64(nn)

			// 限速
			nsec := (float64(nn)/1024/conf.GetBandwidth() - time.Since(start).Seconds()) * 1000 * 1000 * 1000
			if nsec > 0 {
				time.Sleep(time.Duration(nsec))
			}

			return nSizeWrote < block.size
		}) == nil
	}
	return false
}

/**
 * @description: 释放通道资源
 * @param {*}
 * @return {*}
 */
func (fb FileBlock) ReleaseChans() {
	close(fb.bchan)
}
